﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 
// See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ActorStressTests
{
    internal static class Stress
    {
        public static void Assert(bool condition)
        {
            Assert(condition, "Assertion Failed");
        }

        public static void Assert(bool condition, string message)
        {
            if (!condition) throw new Exception(message);
        }

        public static void AssertAreEqual(Object x, Object y, String message)
        {
            if ((x == null) ^ (y == null))
                throw new Exception(String.Format("Assertion failed: One value is null while the other isn't.  {0}", message));
            if (!x.Equals(y))
                throw new Exception(String.Format("Assertion failed: {0}", message));
        }
    }

    class MenuItem
    {
        Action m_action;
        public string Description {get; private set;}

        public MenuItem(string description, Action action)
        {
            Description = description;
            m_action = action;
        }

        public void Run()
        {
            m_action();
        }

    }

    class Menu
    {
        List<MenuItem> m_menuItems = new List<MenuItem>();

        public void AddMenuItem(MenuItem item)
        {
            m_menuItems.Add(item);
        }

        public void ShowOptions()
        {
            Console.WriteLine("\n\n");
            for (int i = 0; i < m_menuItems.Count; i++)
            {
                Console.WriteLine("{0}: {1}", i, m_menuItems[i].Description);
            }
            Console.WriteLine("\na: all\nl: loopall\nl <test1> <test2> ...: loop selected tests\nq: quit\n");
        }

        public void ProcessingLoop()
        {
            while (true)
            {
                ShowOptions();
                Console.Write("Choice? ");
                var choice = Console.ReadLine();

                if (choice == "q") return;
                if (choice == "a")
                {
                    foreach (var item in m_menuItems) item.Run();
                }
                else if (choice == "l")
                {
                    while (true)
                    {
                        Console.WriteLine("\n*** Iteration starting at {0}", DateTime.Now);
                        foreach (var item in m_menuItems) item.Run();
                    }
                }
                else if (choice.StartsWith("l "))
                {
                    var lexemes = choice.Split(' ');
                    var chosenTests = lexemes.Skip(1).Select(s => Int32.Parse(s)).Select(i => m_menuItems[i]).ToArray();
                    while (true)
                    {
                        Console.WriteLine("\n*** Iteration starting at {0}", DateTime.Now);
                        foreach (var item in chosenTests) item.Run();
                    }
                }
                else
                {
                    int index;
                    if (Int32.TryParse(choice, out index))
                    {
                        if (index >= 0 && index < m_menuItems.Count)
                        {
                            var item = m_menuItems[index];
                            item.Run();
                        }
                        else Console.WriteLine("Invalid test index");
                    }
                    else Console.WriteLine("Invalid entry");
                }
            }
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            var fabricAddress = (args.Length > 0) ? args[0] : "127.0.0.1:19000";
            // If not specified, our port number should be 19000.  (Otherwise we pick an odd port like 808.)
            if (fabricAddress.Equals("localhost") || fabricAddress.Equals("127.0.0.1") || fabricAddress.Equals("[::1]"))
                fabricAddress += ":19000";
            
            bool useGateway = fabricAddress.Contains("cloudapp.net");
            if (args.Length >= 2)
            {
                if (args[1].ToLower() == "-gateway" || args[1].ToLower() == "-usegateway")
                    useGateway = true;
                else if (args[1].ToLower() == "-nogateway")
                    useGateway = false;
            }

            Console.WriteLine("Connecting to {0} {1}using a gateway", fabricAddress, useGateway ? "" : "without ");

            Menu menu = new Menu();
            menu.AddMenuItem(new MenuItem("List Push/Pop Test 1", () =>
                {
                ListTests.ListPushPopTest1(fabricAddress, useGateway, 10, 1000, "left");
                ListTests.ListPushPopTest1(fabricAddress, useGateway, 10, 1000, "right");
                }));
            menu.AddMenuItem(new MenuItem("List Push/Pop Test 1 (stateless)", () =>
            {
                ListTests.ListPushPopTest1(fabricAddress, useGateway, 10, 1000, "left", asStateless: true);
                ListTests.ListPushPopTest1(fabricAddress, useGateway, 10, 1000, "right", asStateless: true);
            }));
            menu.AddMenuItem(new MenuItem("List Push/Pop Test 2", () =>
                {
                    ListTests.ListPushPopTest2(fabricAddress, useGateway, 10, 200, "left");
                    ListTests.ListPushPopTest2(fabricAddress, useGateway, 10, 200, "right");
                }));
            menu.AddMenuItem(new MenuItem("List Push/Pop Test 2 (stateless)", () =>
            {
                ListTests.ListPushPopTest2(fabricAddress, useGateway, 10, 200, "left", asStateless: true);
                ListTests.ListPushPopTest2(fabricAddress, useGateway, 10, 200, "right", asStateless: true);
            }));
            menu.AddMenuItem(new MenuItem("List InsertAt/UpdateItemAt/RemoveAt Test", () =>
                {
                    ListTests.InsertUpdateRemoveTest(fabricAddress, useGateway, 10, 1000, 10, 5);
                    ListTests.InsertUpdateRemoveTest(fabricAddress, useGateway, 10, 1000, 10, 0);
                    ListTests.InsertUpdateRemoveTest(fabricAddress, useGateway, 10, 1000, 10, 10);
                }));
            menu.AddMenuItem(new MenuItem("List Create/Use Test", () =>
                {
                    ListTests.ListCreateUseTest(fabricAddress, useGateway, 30);
                }));
            menu.AddMenuItem(new MenuItem("List InsertAt Test", () =>
                {
                    ListTests.ListAddTest(fabricAddress, useGateway, 10, 1000);
                }));
            menu.AddMenuItem(new MenuItem("Perf -- ConcurrentListAdd 4x1000x2", () =>
                {
                    StressPerf.ConcurrentListAdd(fabricAddress, useGateway, 2, 4, 1000, "xxx");
                }));
            menu.AddMenuItem(new MenuItem("Perf -- ConcurrentListAdd 10x1000x2", () =>
                {
                    StressPerf.ConcurrentListAdd(fabricAddress, useGateway, 2, 10, 1000, "xxx");
                }));
            menu.AddMenuItem(new MenuItem("Perf -- ConcurrentListAdd 4x1000x3", () =>
            {
                StressPerf.ConcurrentListAdd(fabricAddress, useGateway, 3, 4, 1000, "xxx");
            }));
            menu.AddMenuItem(new MenuItem("Perf -- ConcurrentListAdd 10x1000x3", () =>
            {
                StressPerf.ConcurrentListAdd(fabricAddress, useGateway, 3, 10, 1000, "xxx");
            }));
            menu.AddMenuItem(new MenuItem("Actor-Actor Communication Tests", () =>
                {
                    CommunicationTests.PingPongPubSub(fabricAddress, useGateway, 1000);
                    CommunicationTests.PingPongFNF(fabricAddress, useGateway, 1000);
                    CommunicationTests.PingPongAsync(fabricAddress, useGateway, 1000);
                    CommunicationTests.Downcounter(fabricAddress, useGateway, 1000);
                }));
            menu.AddMenuItem(new MenuItem("Actor-Actor downcounter (stateless)", () =>
            {
                CommunicationTests.DowncounterStateless(fabricAddress, useGateway, 1000);
            }));
            menu.AddMenuItem(new MenuItem("Create/Delete other actors", () =>
                {
                    CommunicationTests.ActorLifespanManagement(fabricAddress, useGateway, 3);
                }));
            menu.AddMenuItem(new MenuItem("Publish/Subscribe deadlock avoidance test", () =>
                {
                    CommunicationTests.PublishSubscribeDeadlockAvoidanceTest(fabricAddress, useGateway);
                }));

            menu.AddMenuItem(new MenuItem("CloudList<String> test", () =>
                {
                    ListTests.TestCloudListOfString(fabricAddress, useGateway);
                }));
            menu.AddMenuItem(new MenuItem("CloudList<T> Numeric tests", () =>
                {
                    ListTests.TestNumericTypes(fabricAddress, useGateway);
                }));
            menu.AddMenuItem(new MenuItem("Perf -- Add, sort and remove 1 million ints to CloudList<int>", () =>
                {
                    StressPerf.PerfAddOneMillionItems(fabricAddress, useGateway);
                }));

            menu.AddMenuItem(new MenuItem("CloudStringDictionary test with ints", () =>
                {
                    DictionaryTests.StringDictionaryTest(fabricAddress, useGateway);
                }));
            menu.AddMenuItem(new MenuItem("CloudStringDictionary test with Customer objects", () =>
                {
                    DictionaryTests.StringDictionaryTestWithCustomerObjects(fabricAddress, useGateway);
                }));
            menu.AddMenuItem(new MenuItem("CloudDictionary<Int, Float> Test", () =>
                {
                    DictionaryTests.CloudDictionaryIntFloatTest(fabricAddress, useGateway);
                }));
            menu.AddMenuItem(new MenuItem("CloudDictionary<Customer, List<Order>> Test", () =>
                {
                    DictionaryTests.CloudDictionaryCustomerOrderTest(fabricAddress, useGateway);
                }));
            menu.AddMenuItem(new MenuItem("CloudDictionary Numerics Test", () =>
                {
                    DictionaryTests.CloudDictionaryNumericsTest(fabricAddress, useGateway);
                }));
#if CUSTOM_PARTITIONING_AND_CONNECTION_SHARING
            menu.AddMenuItem(new MenuItem("CloudList Partitioning Creation Perf Test", () =>
            {
                ListTests.CloudListPartitioningCreationPerfTest(fabricAddress);
            }));
            menu.AddMenuItem(new MenuItem("CloudList Partitioning Perf Test", () =>
            {
                ListTests.CloudListPartitioningPerfTest(fabricAddress);
            }));
#endif
            menu.AddMenuItem(new MenuItem("CloudList WinFab-Style Partitioning Creation Perf Test", () =>
            {
                ListTests.CloudListPartitioningWinFabStyleCreationPerfTest(fabricAddress, useGateway);
            }));
            menu.AddMenuItem(new MenuItem("CloudList WinFab-Style Partitioning Perf Test", () =>
            {
                ListTests.CloudListPartitioningWinFabStylePerfTest(fabricAddress, useGateway);
            }));
            menu.AddMenuItem(new MenuItem("CloudList WinFab-Style Partitioning Perf Test2", () =>
            {
                ListTests.CloudListPartitioningWinFabStylePerfTest2(fabricAddress, useGateway);
            }));
            menu.AddMenuItem(new MenuItem("CloudDictionary Partitioning Test", () =>
            {
                DictionaryTests.CloudDictionaryPartitionedTest(fabricAddress, useGateway);
            }));
            menu.AddMenuItem(new MenuItem("Partitioned parallel actor method call perf", () =>
            {
                PartitioningTests.ParallelLongRunningMethodCallsOnPartition(fabricAddress, useGateway);
            }));
            menu.AddMenuItem(new MenuItem("Stateless List Partitioning Test", () =>
            {
                ListTests.NonReplicatingPartitionedListTest(fabricAddress, useGateway);
            }));
            // This test's performance is currently so horrendous that I do not want it included in our standard test suite.  (Yet.)
            menu.AddMenuItem(new MenuItem("A2A Communications Torture Test", () =>
            {
                PartitioningTests.PartitionCommunicationTortureTest(fabricAddress, useGateway, 5, 100);
            }));
            /*  // This currently deadlocks in our infrastructure, demonstrating that actor to actor sync calls on the same IActorState are currently not a good idea.
            menu.AddMenuItem(new MenuItem("Actor to Actor Synchronous method call test", () =>
            {
                PartitioningTests.ActorToActorSynchronousCall(fabricAddress, useGateway);
            }));
            */
            menu.AddMenuItem(new MenuItem("EventSource perf test", () =>
            {
                StressPerf.EventSourcePerfTest(fabricAddress, useGateway);
            }));

            menu.ProcessingLoop();

            Console.WriteLine("Press <Enter> to exit");
            Console.ReadLine();
        }
    }
}
